이미지 로딩 중에 레이아웃이 밀리는 문제 해결하기

📅 2022. 03. 24

서론

내가 클릭하려던 버튼이 갑자기 밑으로 슈슉-하고 움직였던 적이 있으신가요? 레이아웃이 움직이면서 의도치 않게 다른 버튼을 눌러버리는 경험을 한 번 쯤 겪어보셨을 겁니다. 브라우저의 렌더링 이후에 엘리먼트의 크기나 위치가 변경되어 레이아웃이 이동하는 것을 Layout Shift(레이아웃 이동)라고 합니다.

예상치 못한 레이아웃 이동은 사용자 경험을 떨어뜨릴뿐만 아니라 작지만 SEO 점수에도 영향을 줍니다.

구글에서는 CLS(Cumulative Layout Shift)라는 지표를 만들어서 사용자가 예상치 못한 레이아웃 이동을 얼마나 경험하는지를 측정하고 있습니다. 크롬 개발자 도구의 Lighthouse를 쓰면 해당 사이트의 CLS 지표를 측정할 수 있습니다.

최근에 저희 팀에서 개발한 아기상어 키즈 월드 웹뷰 페이지에서 Lighthouse를 실행해 보았더니 CLS 경고가 나왔습니다. 실제로 테스트해 본 결과 이미지 로딩 과정에서 레이아웃이 이동하는 현상을 발견해서 개선 작업을 하게 되었습니다.

이 글에서는 CLS 용어와 이미지 태그에 폭 넓이를 지정하는 게 어떻게 Layout Shift 문제를 해결하는지에 대해서 알아보도록 하겠습니다.

Cumulative Layout Shift(누적 레이아웃 이동, CLS)

CLS는 사용자가 예상치 못한 레이아웃 이동을 경험하는 빈도를 수량화 한 지표입니다. CLS의 점수는 모든 예상치 못한 레이아웃 이동 중 가장 큰 이동 점수를 측정한 결과 값이고 낮을수록 좋습니다.

Lighthouse 측정 결과 CLS 수치가 0.799로 굉장히 높게 측정이 되었습니다 🥲 육안으로 봤을 땐 괜찮아 보였는데.. 무슨 일이 있었던 걸까요?

이미지 로딩 과정에서의 레이아웃 이동 문제

레이아웃 이동은 빠른 인터넷 환경에서는 잘 느껴지지 않을 수 있습니다. 감사하게도 크롬 네트워크 탭에서 지원하는 쓰로틀링 기능을 사용해서 느린 인터넷 환경을 재현할 수 있었습니다. 아래는 크롬 Network 탭의 throttling 옵션을 Fast 3G로 설정하고 페이지를 로드한 영상입니다.

이미지를 불러오는 과정에서 reflow가 일어나면서 레이아웃이 밀려나는 것을 볼 수 있습니다.

이미지 크기 지정해서 reflow 방지하기

img 태그의 width, height를 지정하면 브라우저가 이미지를 다운로드해서 표시하기 전에 이미지가 표시될 충분한 영역을 미리 할당합니다.

<img src="URL" width="560" height="245" />

저는 데스크톱 기준으로 width="560"height="245"를 지정하고 CSS는 따로 주지 않았습니다. 한 번 PC 크롬에서 확인해볼까요?

쓰로틀링을 걸고 새로고침을 해보면 미리 이미지 영역을 할당한 덕에 레이아웃 이동이 일어나지 않고 렌더링이 잘 되는 것을 확인할 수 있습니다. iPhone 5/SE (가로 320) 크기에서는 어떨까요?

앗.. 모바일 환경에선 이미지가 잘려 보이네요. 이미지에 CSS를 넣어서 해결해 보겠습니다.

image {
  width: 100%;
  height: auto;
}

width100%로 줘서 부모 컨테이너의 크기에 맞게 폭이 조절되게 하고, height: auto;를 통해 width에 맞게 이미지 높이를 가변적으로 조절되게 변경했습니다. 결과물이 어떻게 나오는지 확인해 봅시다.

확인해 보면 모바일 화면에서도 Layout shift 없이 이미지가 적절한 크기로 렌더링이 되는 것을 확인할 수 있습니다. 개발자 도구에 들어가서 Lighthouse도 다시 측정해 보았습니다.

0.799점에서 0.226점으로 아까보다 훨씬 점수가 좋아진 것을 확인할 수 있습니다.

근데, 뭔가 이상한 점이 느껴지지 않으신가요..? 처음에 지정한 img 태그의 width="560", height="245"는 데스크톱 기준의 높이인데 어떻게 모바일 화면에서 레이아웃 이동 없이 이미지가 적절한 크기로 렌더링 되는 것일까요?

해답은 브라우저의 스타일 시트에 적용된 aspect-ratio(종횡비) 속성에 있습니다.

aspect-ratio

aspect-ratio CSS 속성은 레이아웃 렌더링 초기 단계에서 width, height 속성값을 기반으로 종횡비를 계산하는 속성입니다.

이 종횡비 정보는 auto 크기를 계산할 때 사용됩니다. 예를 들어 width:100%; height:auto; 스타일이 지정된 이미지의 height를 계산할 때 width와의 종횡비를 고려해서 올바른 높이 값이 계산됩니다.

aspect-ratio 속성은 2019년 6월 7일 CSS Working Group의 Jen Simmons가 제안한 A more elegant and easier to use solution의 내용이 Firefox 71 릴리즈에서 브라우저 스타일시트에 기본값으로 적용된 것을 시작으로, 현재는 IE를 제외한 모든 브라우저에 적용되었습니다.

이 해결 방안은 아래의 3가지 조건이 참일때 레이아웃 이동 없이 이미지가 로딩되기 전에 브라우저가 올바른 이미지 크기를 계산하게 도와줍니다.

  1. 엘리먼트에 width 속성이 지정됨
  2. 엘리먼트에 height 속성이 지정됨
  3. a) CSS에서 width 값이 지정되었으며 (% 포함) heightauto
  4. b) CSS에서 height 값이 지정되었으며 (% 포함) widthauto

이론에 기반하면 widthheight를 이미지의 폭/넓이가 아닌 비율로 지정해도 자동으로 크기가 맞춰지지 않을까라는 생각이 들어서 실험해 봤습니다.

엘리먼트에 이미지 원본 비율인 16:7을 width, height 속성으로 넣고 테스트해 본 결과 보시는 것처럼 브라우저가 자동으로 올바른 이미지 크기를 계산한 것을 확인할 수 있었습니다. 단, 이렇게 비율로 width와 height를 지정하는 것은 권장되는 방법은 아닙니다. 느린 인터넷 환경 등의 이유로 CSS를 불러오지 못했을 때 엘리먼트가 width, height를 정직하게 지정한 숫자의 픽셀 그대로 렌더링 하는 상황이 발생할 수 있기 때문입니다.

이미지 엘리먼트의 Layout Shift 문제의 히스토리를 처음부터 알고 싶으신 분들은 Smash Magainze의 setting-height-width-images-important-again 글을 읽어보시는 것을 추천드립니다.

마치며

프로젝트를 대부분 반응형으로 구현하다 보니 이미지 엘리먼트에 width, height 속성을 명시하지 않고 CSS를 통해 가변적으로 이미지 크기를 정하는 일이 많았는데요. 이제부터 엘리먼트에 widthheight를 반드시 기입해야겠다고 다짐하는 계기가 되었습니다.

문제의 히스토리를 보면서 인상적이었던 것은 종횡비를 위한 새로운 속성을 만들지 않고 <img />의 고전적인 width, height 속성을 사용해서 종횡비를 계산하는 아이디어였습니다. 새로운 속성을 도입하는 방법은 간편하지만 채택에 대한 논의가 있어야 하고 개발자들도 새로운 속성을 공부해야 하는 부담이 생깁니다. 이런 문제점을 기존에 있던 속성을 이용해서 해결한 것이 흥미로웠습니다.

참고 문서